<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传测试页面</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }

        .container {
            background: white;
            border-radius: 8px;
            padding: 30px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            margin-bottom: 20px;
        }

        h1 {
            color: #333;
            text-align: center;
            margin-bottom: 30px;
        }

        h2 {
            color: #666;
            border-bottom: 2px solid #007bff;
            padding-bottom: 10px;
            margin: 30px 0 20px 0;
        }

        .upload-section {
            margin-bottom: 40px;
        }

        .form-group {
            margin-bottom: 20px;
        }

        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #555;
        }

        input[type="file"],
        input[type="text"],
        select {
            width: 100%;
            padding: 12px;
            border: 2px solid #ddd;
            border-radius: 6px;
            font-size: 14px;
            transition: border-color 0.3s;
        }

        input[type="file"]:focus,
        input[type="text"]:focus,
        select:focus {
            border-color: #007bff;
            outline: none;
        }

        button {
            background: #007bff;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            margin-right: 10px;
            margin-bottom: 10px;
            transition: background 0.3s;
        }

        button:hover {
            background: #0056b3;
        }

        button:disabled {
            background: #ccc;
            cursor: not-allowed;
        }

        .btn-danger {
            background: #dc3545;
        }

        .btn-danger:hover {
            background: #c82333;
        }

        .btn-success {
            background: #28a745;
        }

        .btn-success:hover {
            background: #218838;
        }

        .btn-warning {
            background: #ffc107;
            color: #212529;
        }

        .btn-warning:hover {
            background: #e0a800;
        }

        .progress-container {
            margin: 20px 0;
        }

        .progress-bar {
            width: 100%;
            height: 20px;
            background: #f0f0f0;
            border-radius: 10px;
            overflow: hidden;
            margin: 10px 0;
        }

        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #007bff, #0056b3);
            border-radius: 10px;
            transition: width 0.3s ease;
            width: 0%;
        }

        .upload-info {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 6px;
            margin: 15px 0;
            border-left: 4px solid #007bff;
        }

        .file-info {
            background: #e9ecef;
            padding: 10px;
            border-radius: 4px;
            margin: 10px 0;
            font-family: monospace;
            font-size: 12px;
        }

        .log-container {
            background: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 6px;
            padding: 15px;
            max-height: 300px;
            overflow-y: auto;
            font-family: monospace;
            font-size: 12px;
        }

        .log-entry {
            margin: 5px 0;
            padding: 5px;
            border-radius: 3px;
        }

        .log-info {
            background: #d1ecf1;
            color: #0c5460;
        }

        .log-success {
            background: #d4edda;
            color: #155724;
        }

        .log-error {
            background: #f8d7da;
            color: #721c24;
        }

        .log-warning {
            background: #fff3cd;
            color: #856404;
        }

        .chunk-list {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
            gap: 5px;
            margin: 15px 0;
        }

        .chunk-item {
            padding: 8px;
            text-align: center;
            border-radius: 4px;
            font-size: 12px;
            font-weight: bold;
        }

        .chunk-pending {
            background: #f8f9fa;
            color: #6c757d;
            border: 1px solid #dee2e6;
        }

        .chunk-uploading {
            background: #fff3cd;
            color: #856404;
            border: 1px solid #ffeaa7;
        }

        .chunk-completed {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }

        .chunk-failed {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }

        .resume-info {
            background: #fff3cd;
            padding: 15px;
            border-radius: 6px;
            margin: 15px 0;
            border-left: 4px solid #ffc107;
        }

        .progress-detail {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin: 15px 0;
        }

        .progress-item {
            background: #f8f9fa;
            padding: 10px;
            border-radius: 4px;
            text-align: center;
        }

        .progress-value {
            font-size: 18px;
            font-weight: bold;
            color: #007bff;
        }

        .progress-label {
            font-size: 12px;
            color: #666;
            margin-top: 5px;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>文件上传测试页面</h1>
        <p style="text-align: center; color: #666;">
            通过网关上传文件: <strong>http://localhost:8080</strong><br>
            API前缀: <strong>/file</strong> | 支持小文件直传和大文件分片上传
        </p>
    </div>

    <!-- 小文件上传 -->
    <div class="container upload-section">
        <h2>小文件上传 (&lt; 50MB)</h2>
        <div class="form-group">
            <label for="smallFile">选择文件:</label>
            <input type="file" id="smallFile" />
        </div>
        <div class="form-group">
            <label for="smallFileCategory">文件分类:</label>
            <select id="smallFileCategory">
                <option value="default">默认</option>
                <option value="image">图片</option>
                <option value="document">文档</option>
                <option value="video">视频</option>
                <option value="audio">音频</option>
            </select>
        </div>
        <button id="uploadSmallFile">上传小文件</button>

        <div id="smallFileProgress" class="progress-container" style="display: none;">
            <div class="progress-bar">
                <div id="smallFileProgressFill" class="progress-fill"></div>
            </div>
            <div id="smallFileProgressText">0%</div>
        </div>

        <div id="smallFileResult" class="upload-info" style="display: none;"></div>
    </div>

    <!-- 大文件分片上传 -->
    <div class="container upload-section">
        <h2>大文件分片上传 (≥ 50MB)</h2>
        <div class="form-group">
            <label for="largeFile">选择文件:</label>
            <input type="file" id="largeFile" />
        </div>
        <div class="form-group">
            <label for="largeFileCategory">文件分类:</label>
            <select id="largeFileCategory">
                <option value="default">默认</option>
                <option value="image">图片</option>
                <option value="document">文档</option>
                <option value="video">视频</option>
                <option value="audio">音频</option>
            </select>
        </div>
        <div class="form-group">
            <label for="chunkSize">分片大小:</label>
            <select id="chunkSize">
                <option value="1048576">1MB</option>
                <option value="2097152">2MB</option>
                <option value="5242880" selected>5MB</option>
                <option value="10485760">10MB</option>
            </select>
        </div>



        <!-- 断点续传区域 -->
        <div class="form-group">
            <label for="resumeUploadId">断点续传 (可选):</label>
            <input type="text" id="resumeUploadId" placeholder="输入之前的uploadId进行断点续传" />
            <button id="checkResumeInfo" class="btn-warning">检查续传信息</button>
        </div>

        <div id="resumeInfo" class="resume-info" style="display: none;"></div>

        <button id="initChunkUpload">初始化分片上传</button>
        <button id="startChunkUpload" disabled>开始分片上传</button>
        <button id="pauseChunkUpload" disabled>暂停上传</button>
        <button id="resumeChunkUpload" disabled>继续上传</button>
        <button id="cancelChunkUpload" disabled class="btn-danger">取消上传</button>
        <button id="queryProgress" disabled class="btn-warning">查询进度</button>
        <button id="fixMissingChunks" disabled class="btn-warning">补传缺失分片</button>

        <div id="largeFileInfo" class="file-info" style="display: none;"></div>

        <!-- 详细进度信息 -->
        <div id="progressDetail" class="progress-detail" style="display: none;">
            <div class="progress-item">
                <div id="progressPercent" class="progress-value">0%</div>
                <div class="progress-label">完成进度</div>
            </div>
            <div class="progress-item">
                <div id="progressChunks" class="progress-value">0/0</div>
                <div class="progress-label">分片进度</div>
            </div>
            <div class="progress-item">
                <div id="progressSize" class="progress-value">0MB</div>
                <div class="progress-label">已上传大小</div>
            </div>
            <div class="progress-item">
                <div id="progressStatus" class="progress-value">-</div>
                <div class="progress-label">上传状态</div>
            </div>
        </div>

        <div id="largeFileProgress" class="progress-container" style="display: none;">
            <div class="progress-bar">
                <div id="largeFileProgressFill" class="progress-fill"></div>
            </div>
            <div id="largeFileProgressText">0% (0/0 分片)</div>
        </div>

        <div id="chunkStatus" class="chunk-list" style="display: none;"></div>

        <div id="largeFileResult" class="upload-info" style="display: none;"></div>
    </div>

    <!-- 操作日志 -->
    <div class="container">
        <h2>操作日志</h2>
        <button id="clearLogs" class="btn-danger">清空日志</button>
        <div id="logContainer" class="log-container"></div>
    </div>

    <!-- 引入SparkMD5库用于计算文件MD5 -->
    <script src="https://cdn.jsdelivr.net/npm/spark-md5@3.0.2/spark-md5.min.js"></script>

    <script>
        // 全局变量
        const BASE_URL = 'http://localhost:8080';  // 网关地址
        const API_PREFIX = '/file';  // API前缀
        let currentUploadId = null;
        let currentFile = null;
        let totalChunks = 0;
        let uploadedChunks = new Set();
        let isUploading = false;
        let isPaused = false;
        let chunks = [];
        let currentChunkIndex = 0;
        let progressTimer = null;

        // 重试配置
        const RETRY_CONFIG = {
            maxRetries: 3,           // 最大重试次数
            baseDelay: 1000,         // 基础延迟(ms)
            maxDelay: 8000,          // 最大延迟(ms)
            backoffFactor: 2,        // 指数退避因子
            networkTimeoutRetries: 5 // 网络超时重试次数
        };

        // 重试状态跟踪
        const retryTracker = new Map(); // chunkNumber -> {attempts, lastError, nextRetryTime}

        // 指数退避延迟计算
        function calculateRetryDelay(attempt) {
            const delay = Math.min(
                RETRY_CONFIG.baseDelay * Math.pow(RETRY_CONFIG.backoffFactor, attempt - 1),
                RETRY_CONFIG.maxDelay
            );
            return delay + Math.random() * 1000; // 添加随机抖动
        }

        // 等待指定时间
        function sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        // 格式化文件大小
        function formatFileSize(bytes) {
            if (!bytes || bytes === 0) return '0 B';

            const k = 1024;
            const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));

            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }

        // 日志功能
        function addLog(message, type = 'info') {
            const logContainer = document.getElementById('logContainer');
            const logEntry = document.createElement('div');
            logEntry.className = `log-entry log-${type}`;
            logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
            logContainer.appendChild(logEntry);
            logContainer.scrollTop = logContainer.scrollHeight;
        }

        document.getElementById('clearLogs').addEventListener('click', () => {
            document.getElementById('logContainer').innerHTML = '';
        });

        // 计算文件MD5
        async function calculateFileMD5(file) {
            return new Promise((resolve, reject) => {
                const spark = new SparkMD5.ArrayBuffer();
                const fileReader = new FileReader();
                const chunkSize = 2097152; // 2MB per chunk
                let currentChunk = 0;
                const chunks = Math.ceil(file.size / chunkSize);

                fileReader.onload = function (e) {
                    spark.append(e.target.result);
                    currentChunk++;

                    if (currentChunk < chunks) {
                        loadNext();
                    } else {
                        resolve(spark.end());
                    }
                };

                fileReader.onerror = function () {
                    reject('MD5计算失败');
                };

                function loadNext() {
                    const start = currentChunk * chunkSize;
                    const end = Math.min(start + chunkSize, file.size);
                    fileReader.readAsArrayBuffer(file.slice(start, end));
                }

                loadNext();
            });
        }

        // 计算分片MD5
        async function calculateChunkMD5(chunk) {
            return new Promise((resolve, reject) => {
                const spark = new SparkMD5.ArrayBuffer();
                const fileReader = new FileReader();

                fileReader.onload = function (e) {
                    spark.append(e.target.result);
                    resolve(spark.end());
                };

                fileReader.onerror = function () {
                    reject('分片MD5计算失败');
                };

                fileReader.readAsArrayBuffer(chunk);
            });
        }

        // 小文件上传
        document.getElementById('uploadSmallFile').addEventListener('click', async () => {
            const fileInput = document.getElementById('smallFile');
            const categorySelect = document.getElementById('smallFileCategory');
            const file = fileInput.files[0];

            if (!file) {
                alert('请选择文件');
                return;
            }

            if (file.size > 50 * 1024 * 1024) {
                alert('文件大小超过50MB，请使用分片上传');
                return;
            }

            const progressContainer = document.getElementById('smallFileProgress');
            const progressFill = document.getElementById('smallFileProgressFill');
            const progressText = document.getElementById('smallFileProgressText');
            const resultDiv = document.getElementById('smallFileResult');

            progressContainer.style.display = 'block';
            resultDiv.style.display = 'none';

            addLog(`开始上传小文件: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)}MB)`);

            const formData = new FormData();
            formData.append('file', file);
            formData.append('category', categorySelect.value);

            try {
                const xhr = new XMLHttpRequest();

                xhr.upload.addEventListener('progress', (e) => {
                    if (e.lengthComputable) {
                        const percentComplete = Math.round((e.loaded / e.total) * 100);
                        progressFill.style.width = percentComplete + '%';
                        progressText.textContent = percentComplete + '%';
                    }
                });

                xhr.onload = function () {
                    if (xhr.status === 200) {
                        const response = JSON.parse(xhr.responseText);
                        if (response.code === 200) {
                            addLog('小文件上传成功', 'success');
                            resultDiv.innerHTML = `
                                <strong>上传成功!</strong><br>
                                文件ID: ${response.data.fileId}<br>
                                原始文件名: ${response.data.filename}<br>
                                文件大小: ${response.data.fileSize}<br>
                                下载链接: ${response.data.downloadUrl}
                            `;
                            resultDiv.style.display = 'block';
                        } else {
                            addLog(`小文件上传失败: ${response.message}`, 'error');
                            alert('上传失败: ' + response.message);
                        }
                    } else {
                        addLog(`小文件上传失败: HTTP ${xhr.status}`, 'error');
                        alert('上传失败');
                    }
                    progressContainer.style.display = 'none';
                };

                xhr.onerror = function () {
                    addLog('小文件上传网络错误', 'error');
                    alert('网络错误');
                    progressContainer.style.display = 'none';
                };

                xhr.open('POST', `${BASE_URL}${API_PREFIX}/upload`);
                xhr.send(formData);

            } catch (error) {
                addLog(`小文件上传异常: ${error.message}`, 'error');
                alert('上传异常: ' + error.message);
                progressContainer.style.display = 'none';
            }
        });

        // 文件检查接口 - 支持秒传和断点续传
        async function checkFileBeforeUpload(filename, fileSize, fileMd5, category) {
            try {
                const checkRequest = {
                    filename: filename,
                    fileSize: fileSize,
                    fileMd5: fileMd5,
                    category: category
                };

                const response = await fetch(`${BASE_URL}${API_PREFIX}/upload/check`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(checkRequest)
                });

                const result = await response.json();
                if (result.code === 200) {
                    return result.data;
                }
                return null;
            } catch (error) {
                addLog(`文件检查异常: ${error.message}`, 'error');
                return null;
            }
        }

        // 检查断点续传信息
        document.getElementById('checkResumeInfo').addEventListener('click', async () => {
            const resumeUploadId = document.getElementById('resumeUploadId').value.trim();
            if (!resumeUploadId) {
                alert('请输入uploadId');
                return;
            }

            try {
                addLog(`检查断点续传信息: ${resumeUploadId}`);

                const response = await fetch(`${BASE_URL}${API_PREFIX}/upload/progress/${resumeUploadId}`);
                const result = await response.json();

                if (result.code === 200) {
                    const progressData = result.data;
                    const resumeInfoDiv = document.getElementById('resumeInfo');

                    if (progressData.status === 'UPLOADING' && progressData.completedChunks < progressData.totalChunks) {
                        resumeInfoDiv.innerHTML = `
                            <strong>可以续传!</strong><br>
                            文件名: ${progressData.filename}<br>
                            完成进度: ${progressData.progress.toFixed(1)}% (${progressData.completedChunks}/${progressData.totalChunks})<br>
                            状态: ${progressData.status}<br>
                            建议: 可以继续上传剩余分片
                        `;

                        // 设置当前uploadId并启用相关按钮
                        currentUploadId = resumeUploadId;
                        currentFile = {
                            name: progressData.filename,
                            size: progressData.fileSize || 0,
                            md5: null  // 需要重新选择文件获取MD5
                        };
                        totalChunks = progressData.totalChunks;

                        // 更新已上传分片信息
                        uploadedChunks.clear();
                        if (progressData.completedChunkNumbers) {
                            progressData.completedChunkNumbers.forEach(num => uploadedChunks.add(num));
                        }

                        document.getElementById('startChunkUpload').disabled = false;
                        document.getElementById('resumeChunkUpload').disabled = false;
                        document.getElementById('cancelChunkUpload').disabled = false;
                        document.getElementById('queryProgress').disabled = false;

                        updateProgress();
                        addLog(`发现可续传任务: ${progressData.filename}, 进度: ${progressData.progress.toFixed(1)}%`, 'success');
                    } else {
                        resumeInfoDiv.innerHTML = `
                            <strong>无法续传</strong><br>
                            状态: ${progressData.status}<br>
                            进度: ${progressData.progress.toFixed(1)}%<br>
                            建议: ${progressData.status === 'COMPLETED' ? '文件已完成上传' : '上传会话可能已过期'}
                        `;
                        addLog(`无法续传: 状态 ${progressData.status}`, 'warning');
                    }

                    resumeInfoDiv.style.display = 'block';
                } else {
                    addLog(`检查续传信息失败: ${result.message}`, 'error');
                    alert('检查失败: ' + result.message);
                }
            } catch (error) {
                addLog(`检查续传信息异常: ${error.message}`, 'error');
                alert('检查异常: ' + error.message);
            }
        });

        // 查询进度
        document.getElementById('queryProgress').addEventListener('click', async () => {
            if (!currentUploadId) {
                alert('没有活跃的上传任务');
                return;
            }

            try {
                const response = await fetch(`${BASE_URL}${API_PREFIX}/upload/progress/${currentUploadId}`);
                const result = await response.json();

                if (result.code === 200) {
                    const progressData = result.data;

                    // 更新进度显示
                    updateProgressDisplay(progressData);

                    addLog(`进度查询: ${progressData.progress}%, ${progressData.completedChunks}/${progressData.totalChunks} 分片`, 'info');
                } else {
                    addLog(`查询进度失败: ${result.message}`, 'error');
                }
            } catch (error) {
                addLog(`查询进度异常: ${error.message}`, 'error');
            }
        });

        // 更新进度显示
        function updateProgressDisplay(progressData) {
            if (!progressData) return;

            // 更新详细进度信息
            document.getElementById('progressPercent').textContent = progressData.progress.toFixed(1) + '%';
            document.getElementById('progressChunks').textContent = `${progressData.completedChunks}/${progressData.totalChunks}`;

            // 计算已上传大小
            const uploadedSize = Math.round((progressData.progress / 100) * (currentFile ? currentFile.size : 0));
            document.getElementById('progressSize').textContent = formatFileSize(uploadedSize);
            document.getElementById('progressStatus').textContent = progressData.status;

            // 更新进度条
            const progressFill = document.getElementById('largeFileProgressFill');
            const progressText = document.getElementById('largeFileProgressText');

            progressFill.style.width = progressData.progress + '%';
            progressText.textContent = `${progressData.progress.toFixed(1)}% (${progressData.completedChunks}/${progressData.totalChunks} 分片)`;

            // 显示进度区域
            document.getElementById('progressDetail').style.display = 'grid';
            document.getElementById('largeFileProgress').style.display = 'block';

            // 更新已上传分片信息
            if (progressData.completedChunkNumbers) {
                uploadedChunks.clear();
                progressData.completedChunkNumbers.forEach(num => uploadedChunks.add(num));
                updateChunkStatus();
            }
        }

        // 自动查询进度（定时器）
        function startProgressPolling() {
            if (progressTimer) {
                clearInterval(progressTimer);
            }

            progressTimer = setInterval(async () => {
                if (currentUploadId && isUploading) {
                    try {
                        const response = await fetch(`${BASE_URL}${API_PREFIX}/upload/progress/${currentUploadId}`);
                        const result = await response.json();

                        if (result.code === 200) {
                            updateProgressDisplay(result.data);
                        }
                    } catch (error) {
                        // 静默处理定时查询错误
                    }
                }
            }, 2000); // 每2秒查询一次
        }

        function stopProgressPolling() {
            if (progressTimer) {
                clearInterval(progressTimer);
                progressTimer = null;
            }
        }

        // 初始化分片上传
        document.getElementById('initChunkUpload').addEventListener('click', async () => {
            const fileInput = document.getElementById('largeFile');
            const categorySelect = document.getElementById('largeFileCategory');
            const chunkSizeSelect = document.getElementById('chunkSize');
            const file = fileInput.files[0];

            if (!file) {
                alert('请选择文件');
                return;
            }

            currentFile = {
                name: file.name,
                size: file.size,
                md5: null  // 将在计算MD5后设置
            };
            const chunkSize = parseInt(chunkSizeSelect.value);
            totalChunks = Math.ceil(file.size / chunkSize);

            addLog(`开始计算文件MD5: ${file.name}`);

            try {
                const fileMD5 = await calculateFileMD5(file);
                currentFile.md5 = fileMD5;  // 保存MD5用于后续合并
                addLog(`文件MD5计算完成: ${fileMD5}`);

                // 切分文件
                chunks = [];
                for (let i = 0; i < totalChunks; i++) {
                    const start = i * chunkSize;
                    const end = Math.min(start + chunkSize, file.size);
                    chunks.push(file.slice(start, end));
                }

                // 显示文件信息
                const fileInfoDiv = document.getElementById('largeFileInfo');
                fileInfoDiv.innerHTML = `
                    <strong>文件信息:</strong><br>
                    文件名: ${currentFile.name}<br>
                    文件大小: ${(currentFile.size / 1024 / 1024).toFixed(2)} MB<br>
                    分片大小: ${(chunkSize / 1024 / 1024).toFixed(2)} MB<br>
                    总分片数: ${totalChunks}<br>
                    文件MD5: ${currentFile.md5}
                `;
                fileInfoDiv.style.display = 'block';

                // 先检查文件是否可以秒传或断点续传
                addLog('检查文件状态（秒传/断点续传）...');
                const checkResult = await checkFileBeforeUpload(
                    currentFile.name,
                    currentFile.size,
                    currentFile.md5,
                    categorySelect.value
                );

                if (checkResult) {
                    if (checkResult.status === 'EXIST') {
                        // 文件已存在，秒传成功
                        addLog('文件已存在，秒传成功!', 'success');

                        const resultDiv = document.getElementById('largeFileResult');
                        resultDiv.innerHTML = `
                            <strong>秒传成功!</strong><br>
                            文件ID: ${checkResult.fileId}<br>
                            原始文件名: ${currentFile.name}<br>
                            文件大小: ${formatFileSize(currentFile.size)}<br>
                            下载链接: ${checkResult.downloadUrl}
                        `;
                        resultDiv.style.display = 'block';

                        resetUploadState();
                        return;

                    } else if (checkResult.status === 'PARTIAL') {
                        // 支持断点续传
                        addLog(`发现未完成上传，支持断点续传，进度: ${checkResult.progress.toFixed(1)}%`, 'info');

                        currentUploadId = checkResult.uploadId;
                        totalChunks = Math.ceil(currentFile.size / chunkSize);

                        // 更新已上传分片信息
                        uploadedChunks.clear();
                        if (checkResult.completedChunks) {
                            checkResult.completedChunks.forEach(num => uploadedChunks.add(num));
                        }

                        // 启用相关按钮
                        document.getElementById('startChunkUpload').disabled = false;
                        document.getElementById('cancelChunkUpload').disabled = false;
                        document.getElementById('queryProgress').disabled = false;
                        document.getElementById('initChunkUpload').disabled = true;

                        updateProgress();
                        updateChunkStatus();
                        return;
                    }
                }

                // 需要新上传，初始化分片上传
                addLog(`初始化分片上传: ${currentFile.name}, 总分片数: ${totalChunks}`);

                const initRequest = {
                    filename: currentFile.name,
                    fileSize: currentFile.size,
                    fileMd5: currentFile.md5,
                    chunkSize: chunkSize,
                    category: categorySelect.value
                };

                const response = await fetch(`${BASE_URL}${API_PREFIX}/upload/init`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(initRequest)
                });

                const result = await response.json();
                if (result.code === 200) {
                    const initData = result.data;
                    currentUploadId = initData.uploadId;
                    uploadedChunks.clear();

                    addLog(`分片上传初始化成功, uploadId: ${currentUploadId}`, 'success');

                    // 启用相关按钮
                    document.getElementById('startChunkUpload').disabled = false;
                    document.getElementById('cancelChunkUpload').disabled = false;
                    document.getElementById('queryProgress').disabled = false;
                    document.getElementById('initChunkUpload').disabled = true;

                    // 初始化分片状态显示
                    updateChunkStatus();
                } else {
                    addLog(`分片上传初始化失败: ${result.message}`, 'error');
                    alert('初始化失败: ' + result.message);
                }
            } catch (error) {
                addLog(`分片上传初始化异常: ${error.message}`, 'error');
                alert('初始化异常: ' + error.message);
            }
        });

        // 更新分片状态显示
        function updateChunkStatus() {
            const chunkStatusDiv = document.getElementById('chunkStatus');
            chunkStatusDiv.innerHTML = '';

            for (let i = 0; i < totalChunks; i++) {
                const chunkDiv = document.createElement('div');
                chunkDiv.className = 'chunk-item';
                chunkDiv.textContent = i + 1;

                if (uploadedChunks.has(i + 1)) {
                    chunkDiv.className += ' chunk-completed';
                } else if (i === currentChunkIndex && isUploading) {
                    chunkDiv.className += ' chunk-uploading';
                } else {
                    chunkDiv.className += ' chunk-pending';
                }

                chunkStatusDiv.appendChild(chunkDiv);
            }
            chunkStatusDiv.style.display = 'block';
        }

        // 更新进度
        function updateProgress() {
            const progressFill = document.getElementById('largeFileProgressFill');
            const progressText = document.getElementById('largeFileProgressText');
            const progress = Math.round((uploadedChunks.size / totalChunks) * 100);

            progressFill.style.width = progress + '%';
            progressText.textContent = `${progress}% (${uploadedChunks.size}/${totalChunks} 分片)`;

            document.getElementById('largeFileProgress').style.display = 'block';
            updateChunkStatus();
        }

        // 上传单个分片
        async function uploadSingleChunk(chunkIndex) {
            if (isPaused || !isUploading) return false;

            const chunk = chunks[chunkIndex];
            const chunkNumber = chunkIndex + 1;

            try {
                // 计算分片MD5
                const chunkMD5 = await calculateChunkMD5(chunk);

                const formData = new FormData();
                formData.append('uploadId', currentUploadId);
                formData.append('chunkNumber', chunkNumber);
                formData.append('chunk', chunk);
                formData.append('chunkMd5', chunkMD5);

                const response = await fetch(`${BASE_URL}${API_PREFIX}/upload/upload`, {
                    method: 'POST',
                    body: formData
                });

                const result = await response.json();
                if (result.code === 200) {
                    const chunkData = result.data;

                    if (chunkData.status === 'UPLOADED') {
                        uploadedChunks.add(chunkNumber);
                        addLog(`分片 ${chunkNumber}/${totalChunks} 上传成功`);
                    } else if (chunkData.status === 'EXISTS') {
                        uploadedChunks.add(chunkNumber);
                        addLog(`分片 ${chunkNumber}/${totalChunks} 已存在，跳过`);
                    } else {
                        addLog(`分片 ${chunkNumber}/${totalChunks} 状态: ${chunkData.status}`, 'warning');
                    }

                    updateProgress();
                    return true;
                } else {
                    addLog(`分片 ${chunkNumber} 上传失败: ${result.message}`, 'error');
                    return false;
                }
            } catch (error) {
                addLog(`分片 ${chunkNumber} 上传异常: ${error.message}`, 'error');
                return false;
            }
        }

        // 带重试的分片上传
        async function uploadSingleChunkWithRetry(chunkIndex, maxRetries = RETRY_CONFIG.maxRetries) {
            const chunkNumber = chunkIndex + 1;
            let lastError = null;

            for (let attempt = 1; attempt <= maxRetries; attempt++) {
                try {
                    // 检查是否被暂停
                    if (isPaused || !isUploading) {
                        addLog(`分片 ${chunkNumber} 上传被暂停`, 'warning');
                        return false;
                    }

                    // 如果不是第一次尝试，等待退避时间
                    if (attempt > 1) {
                        const delay = calculateRetryDelay(attempt - 1);
                        addLog(`分片 ${chunkNumber} 第${attempt}次重试，等待 ${Math.round(delay / 1000)}秒...`, 'warning');
                        await sleep(delay);
                    }

                    // 尝试上传分片
                    const success = await uploadSingleChunk(chunkIndex);

                    if (success) {
                        // 成功时清理重试记录
                        retryTracker.delete(chunkNumber);
                        addLog(`分片 ${chunkNumber} 上传成功 ${attempt > 1 ? `(第${attempt}次尝试)` : ''}`, 'success');
                        return true;
                    } else {
                        throw new Error('分片上传返回失败状态');
                    }

                } catch (error) {
                    lastError = error;

                    // 更新重试跟踪
                    retryTracker.set(chunkNumber, {
                        attempts: attempt,
                        lastError: error.message,
                        nextRetryTime: Date.now() + calculateRetryDelay(attempt)
                    });

                    if (attempt < maxRetries) {
                        addLog(`分片 ${chunkNumber} 上传失败: ${error.message}, 准备第${attempt + 1}次重试`, 'warning');
                    } else {
                        addLog(`分片 ${chunkNumber} 上传失败，已达最大重试次数: ${error.message}`, 'error');
                    }
                }
            }

            return false; // 所有重试都失败
        }

        // 检查并补传缺失分片
        async function checkAndUploadMissingChunks() {
            if (!currentUploadId) return true;

            try {
                // 查询服务器进度
                const response = await fetch(`${BASE_URL}${API_PREFIX}/upload/progress/${currentUploadId}`);
                const result = await response.json();

                if (result.code === 200) {
                    const progressData = result.data;

                    // 计算缺失的分片
                    const completedSet = new Set(progressData.completedChunkNumbers || []);
                    const missingChunks = [];
                    for (let i = 1; i <= progressData.totalChunks; i++) {
                        if (!completedSet.has(i)) {
                            missingChunks.push(i);
                        }
                    }

                    if (missingChunks.length > 0) {
                        addLog(`发现 ${missingChunks.length} 个缺失分片: [${missingChunks.join(', ')}]`, 'warning');

                        // 更新本地状态
                        uploadedChunks.clear();
                        progressData.completedChunkNumbers.forEach(num => uploadedChunks.add(num));

                        // 逐个补传缺失分片（避免并发问题）
                        for (const chunkNumber of missingChunks) {
                            if (isPaused || !isUploading) break;

                            const success = await uploadSingleChunkWithRetry(chunkNumber - 1);
                            if (!success) {
                                addLog(`分片 ${chunkNumber} 补传失败`, 'error');
                                return false;
                            }
                        }

                        addLog('所有缺失分片补传成功', 'success');
                    }

                    return true;
                } else {
                    addLog(`查询进度失败: ${result.message}`, 'error');
                    return false;
                }
            } catch (error) {
                addLog(`检查缺失分片异常: ${error.message}`, 'error');
                return false;
            }
        }

        // 检查上传完成状态（带重试）
        async function checkUploadCompletionWithRetry() {
            const maxRetries = 3;

            for (let attempt = 1; attempt <= maxRetries; attempt++) {
                try {
                    addLog(`检查上传完成状态 (第${attempt}次)...`);

                    // 检查并补传缺失分片
                    const missingChunksFixed = await checkAndUploadMissingChunks();

                    if (!missingChunksFixed) {
                        if (attempt < maxRetries) {
                            addLog(`补传分片失败，等待重试...`, 'warning');
                            await sleep(2000 * attempt); // 递增延迟
                            continue;
                        } else {
                            addLog('补传分片多次失败，请手动处理', 'error');

                            // 暂停上传，允许用户手动处理
                            isPaused = true;
                            isUploading = false;
                            stopProgressPolling();
                            document.getElementById('pauseChunkUpload').disabled = true;
                            document.getElementById('resumeChunkUpload').disabled = false;
                            document.getElementById('fixMissingChunks').disabled = false;
                            return;
                        }
                    }

                    // 最终确认所有分片完成
                    const finalCheck = await fetch(`${BASE_URL}${API_PREFIX}/upload/progress/${currentUploadId}`);
                    const finalResult = await finalCheck.json();

                    if (finalResult.code === 200) {
                        const finalProgress = finalResult.data;

                        // 计算缺失分片
                        const finalCompletedSet = new Set(finalProgress.completedChunkNumbers || []);
                        const finalMissingChunks = [];
                        for (let i = 1; i <= finalProgress.totalChunks; i++) {
                            if (!finalCompletedSet.has(i)) {
                                finalMissingChunks.push(i);
                            }
                        }

                        if (finalMissingChunks.length === 0) {
                            addLog('所有分片确认完成，开始合并文件', 'success');
                            await completeUpload();
                            return;
                        } else {
                            addLog(`仍有 ${finalMissingChunks.length} 个分片缺失`, 'warning');
                            if (attempt < maxRetries) continue;
                        }
                    }

                } catch (error) {
                    addLog(`检查完成状态异常: ${error.message}`, 'error');
                    if (attempt < maxRetries) {
                        await sleep(2000 * attempt);
                        continue;
                    }
                }
            }

            // 所有重试都失败
            addLog('无法确认所有分片完成，请检查网络或手动重试', 'error');
            isPaused = true;
            isUploading = false;
            stopProgressPolling();
            document.getElementById('pauseChunkUpload').disabled = true;
            document.getElementById('resumeChunkUpload').disabled = false;
            document.getElementById('fixMissingChunks').disabled = false;
        }

        // 开始分片上传
        document.getElementById('startChunkUpload').addEventListener('click', async () => {
            if (!currentUploadId || !chunks.length) {
                alert('请先初始化分片上传');
                return;
            }

            isUploading = true;
            isPaused = false;
            currentChunkIndex = 0;

            document.getElementById('startChunkUpload').disabled = true;
            document.getElementById('pauseChunkUpload').disabled = false;
            document.getElementById('resumeChunkUpload').disabled = true;

            addLog('开始分片上传');
            startProgressPolling();

            // 并发上传控制
            const maxConcurrency = 3;
            let activeUploads = 0;
            let completedCount = 0;

            async function uploadNext() {
                while (currentChunkIndex < totalChunks && activeUploads < maxConcurrency) {
                    if (isPaused || !isUploading) break;

                    const chunkIndex = currentChunkIndex;
                    const chunkNumber = chunkIndex + 1;

                    // 跳过已上传的分片
                    if (uploadedChunks.has(chunkNumber)) {
                        currentChunkIndex++;
                        completedCount++;
                        continue;
                    }

                    activeUploads++;
                    currentChunkIndex++;

                    uploadSingleChunk(chunkIndex).then(success => {
                        activeUploads--;
                        completedCount++;

                        if (success) {
                            // 继续上传下一个分片
                            uploadNext();
                        } else {
                            // 上传失败，暂停上传
                            isPaused = true;
                            isUploading = false;
                            stopProgressPolling();
                            document.getElementById('startChunkUpload').disabled = false;
                            document.getElementById('pauseChunkUpload').disabled = true;
                            document.getElementById('resumeChunkUpload').disabled = false;
                            addLog('上传失败，已暂停', 'error');
                        }

                        // 检查是否所有分片都完成
                        if (uploadedChunks.size === totalChunks && isUploading) {
                            addLog('所有分片上传完成，开始合并文件');
                            completeUpload();
                        }
                    });
                }
            }

            // 开始并发上传
            uploadNext();
        });

        // 暂停上传
        document.getElementById('pauseChunkUpload').addEventListener('click', () => {
            isPaused = true;
            isUploading = false;
            stopProgressPolling();

            document.getElementById('startChunkUpload').disabled = false;
            document.getElementById('pauseChunkUpload').disabled = true;
            document.getElementById('resumeChunkUpload').disabled = false;

            addLog('上传已暂停');
        });

        // 继续上传 (从当前进度继续)
        document.getElementById('resumeChunkUpload').addEventListener('click', () => {
            if (!currentUploadId) {
                alert('没有可恢复的上传任务');
                return;
            }

            // 重新开始上传逻辑
            document.getElementById('startChunkUpload').click();
        });

        // 取消上传（清理本地状态）
        document.getElementById('cancelChunkUpload').addEventListener('click', () => {
            if (!currentUploadId) {
                alert('没有可取消的上传任务');
                return;
            }

            if (!confirm('确定要取消上传吗？这将清理本地状态，服务器上的分片会自动过期。')) {
                return;
            }

            addLog('取消上传，清理本地状态', 'info');
            resetUploadState();
        });

        // 完成分片上传
        async function completeUpload() {
            stopProgressPolling();

            try {
                addLog('开始合并文件...');

                const response = await fetch(`${BASE_URL}${API_PREFIX}/upload/merge`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        uploadId: currentUploadId,
                        fileMd5: currentFile.md5
                    })
                });

                const result = await response.json();
                if (result.code === 200) {
                    addLog('文件合并成功，上传完成!', 'success');

                    const resultDiv = document.getElementById('largeFileResult');
                    const uploadData = result.data;
                    resultDiv.innerHTML = `
                        <strong>上传成功!</strong><br>
                        文件ID: ${uploadData.fileId}<br>
                        原始文件名: ${uploadData.filename}<br>
                        文件大小: ${formatFileSize(uploadData.fileSize)}<br>
                        下载链接: ${uploadData.downloadUrl}
                    `;
                    resultDiv.style.display = 'block';

                    resetUploadState();
                } else {
                    addLog(`文件合并失败: ${result.message}`, 'error');
                    alert('合并失败: ' + result.message);
                }
            } catch (error) {
                addLog(`文件合并异常: ${error.message}`, 'error');
                alert('合并异常: ' + error.message);
            }
        }

        // 重置上传状态
        function resetUploadState() {
            isUploading = false;
            isPaused = false;
            currentUploadId = null;
            currentFile = null;
            totalChunks = 0;
            uploadedChunks.clear();
            chunks = [];
            currentChunkIndex = 0;
            stopProgressPolling();

            document.getElementById('initChunkUpload').disabled = false;
            document.getElementById('startChunkUpload').disabled = true;
            document.getElementById('pauseChunkUpload').disabled = true;
            document.getElementById('resumeChunkUpload').disabled = true;
            document.getElementById('cancelChunkUpload').disabled = true;
            document.getElementById('queryProgress').disabled = true;

            document.getElementById('largeFileProgress').style.display = 'none';
            document.getElementById('progressDetail').style.display = 'none';
            document.getElementById('chunkStatus').style.display = 'none';
            document.getElementById('largeFileInfo').style.display = 'none';
            document.getElementById('resumeInfo').style.display = 'none';

            // 清空断点续传输入框
            document.getElementById('resumeUploadId').value = '';
        }

        // 补传缺失分片按钮
        document.getElementById('fixMissingChunks').addEventListener('click', async () => {
            if (!currentUploadId) {
                alert('没有活跃的上传任务');
                return;
            }

            addLog('开始补传缺失分片...');
            const success = await checkAndUploadMissingChunks();

            if (success) {
                addLog('补传完成', 'success');
                // 检查是否可以完成上传
                if (uploadedChunks.size === totalChunks) {
                    await completeUpload();
                }
            } else {
                addLog('补传失败', 'error');
            }
        });

        // 页面卸载时清理定时器
        window.addEventListener('beforeunload', () => {
            stopProgressPolling();
        });



        // 页面加载完成
        addLog('文件上传测试页面加载完成');
    </script>
</body>

</html>